home *** CD-ROM | disk | FTP | other *** search
-
- #import "Text.h"
- #import "PacManView.h"
- #import "PacManGameBrain.h"
- #import "PacManInfoController.h"
- #import <libc.h> // event stuff, misc.
- #import <daymisckit/daymisckit.h>
- #import "FruitView.h"
- #import "Maze.h"
- #import "Monster.h"
- #import "Player.h"
-
- // comment out the next line to disable the cheat modes. Cheaters can't
- // get net high scores, but they can get local high scores.
- #define CHEATMODES YES
-
- // sound definitions
- #define DOTEATSOUND 0 // any normal dot
- #define POWERDOTEATSOUND 2 // power dot
- #define MONSTEREATSOUND 1 // monsters
- #define FRUITEATSOUND 4 // fruit
- #define DEADSOUND 3 // player nabbed by a ghost
- #define STARTLEVELSOUND 5 // music for start of a game/level
-
- #define POINTS (12.0 * scale) // the size (in points) we draw the text at
- #define READY_X ((x - 2 * GHOST_SIZE) * scale + mazePos.x)
- #define READY_Y (y * scale + mazePos.y)
- #define OFFSET_X (scale * 6)
- #define OFFSET_Y (scale * 3)
- // decides which screen (1-6) to load for a given level. Allows us to
- // put them in an arbitrary order.
- static int screens[NUMSCREENS] = { // which maze image (1-6) to use
- 1, 1, 1, 1, 2, 2, 2, 2,
- 3, 3, 3, 4, 4, 5, 5, 5,
- 1, 2, 0, 0, 3, 4, 0, 5 };
-
- static windowX[3] = { 0.0, 362.0, 698.0 };
- static windowY[3] = { 0.0, 280.0, 536.0 };
-
- @implementation PacManView
-
- - initFrame:(const NXRect *)frm // designated initializer for a view
- {
- [super initFrame:frm];
- backIsColor = NO; // override default; we want image by default
- begin = 128;
-
- // initialize game variables
-
- // where the maze is located within our view.
- mazePos.x = 13; mazePos.y = 12;
- eraseReady = NO;
- myorigin.x = 0; myorigin.y = 0;
- scale = 0;
- // a convenient rect; this way I don't have to keep creating a bunch of
- // rects of dimensions GHOST_SIZE by GHOST_SIZE; I re-use this one...
- NXSetRect(&eraseRect, 0, 0, GHOST_SIZE, GHOST_SIZE);
- NXSetRect(&textEraseRect, 0, 0, 3 * GHOST_SIZE, 2 * GHOST_SIZE);
- NXSetRect(&readyRect, 0, 0, GHOST_SIZE * scale * 5, GHOST_SIZE * scale);
- // make sure power dots get drawn
- erasePwr = YES;
-
- return self;
- }
-
- - setGhostTracker:(GKTrackerId)tracker { ghostId = tracker; return self; }
- - setFruitTracker:(GKTrackerId)tracker { fruitId = tracker; return self; }
-
- // Called by appDidInit to load up images, etc. that we need.
- - loadPix
- {
- int i; id dotSound = [customSound soundNum:DOTEATSOUND type:0];
- const int *gh;
-
- [super loadPix];
-
- // the dots play through teir own private stream with our special params
- [dotSound setPlayStream:[[GKSoundStream alloc] initStreams:1]];
- [dotSound setPercentToPlay:0.01]; // this number works well... :-)
- backGround2 = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size];
- fruitPointCount = 0;
-
- // build the ghost objects
- gh = [maze ghosts];
- for (i=0; i<=3; i++) {
- ghost[i] = [[Monster alloc]
- initGhost:i player:player maze:maze at:gh[i*2] :gh[i*2+1]];
- ghostPointCount[i] = 0;
- }
-
- // get the images
- fruit[1] = [NXImage findImageNamed:"Fruit.tiff"];
- fruit[2] = [NXImage findImageNamed:"FruitBig.tiff"];
- gameOver[1] = [NXImage findImageNamed:"GameOver.tiff"];
- gameOver[2] = [NXImage findImageNamed:"GameOverBig.tiff"];
- [gameOver[1] getSize:&(gameOverSize[1])];
- [gameOver[2] getSize:&(gameOverSize[2])];
- [self setScale:[preferences scale]];
- return self;
- }
-
- - ghost:(int)i // return ghost #i
- {
- return ghost[i];
- }
-
-
- // State machine...called by the timed entry. This state machine basically
- // controls all the aspects of the game; depending upon it's state, different
- // things can/will happen. The best way to figure this one out is to sit
- // down and draw a diagram show how the states transition from one to the
- // next. Note that there are several counters, etc. that function as smaller
- // independent state machines that operate within the context of this larger
- // state machine. (Dots blinking, monster states, etc. all run independently
- // from main state machine, although the main machine occasionally interrupts
- // things in the sub-machines.) I've not yet had time to fully document the
- // logic involved in this state machine...if I ever have the time, I may do so,
- // but I don't know that anyone would care if I did anyway. Minor changes in
- // here could render the entire game non-functional if you aren't careful!
- // UNLESS YOU KNOW WHAT YOU'RE DOING, DON'T MESS WITH THIS CODE! Take time
- // to understand it fully before playing with it!
- // This is the most ridiculously long method you'll ever see, but there's
- // no really efficient way to break it up. (Each section deals with what
- // happens in a specific state, and breaking into smaller methods isn't worth
- // while, since things are repeated...and it's silly to proliferate subroutines
- // that only get called once and from one place.)
- - autoUpdate:sender
- { // ALL animation is controlled from here!!!
- register int i, lcnt;
- register BOOL dotEat = NO;
- int x, y;
- BOOL flag = NO;
- BOOL updateFlag = NO;
- float gx, gy;
- NXRect tRect;
-
- // keep track of how many time we've been called; we can use it do
- // decide when to do certain things...
- if ([NXApp isHidden]) return self; // don't suck cycles if we're hidden
- cycles++;
- if (((![preferences speed]) || ([preferences speed] == 2))
- && (!demoMode)) { // slow speed, so ignore 50% of entries
- if (cycles & 0x01) return self;
- }
-
- if (!(cycles & 0x0f)) { // power dots always blink no matter what.
- [self blinkPowerDot]; // happens every 16 cycles.
- erasePwr = YES;
- }
-
- if (fruitPointCount) {
- if (!--fruitPointCount) {
- fruitPointCount = WIPETEXT;
- } }
-
- for (i=0; i<4; i++) {
- if (ghostPointCount[i]) {
- if (!--ghostPointCount[i]) {
- ghostPointCount[i] = WIPETEXT;
- } } }
-
- if (state == GAMEOVER) {
- // when demowait counter hits WAITFORDEMO, we start up demo mode.
- // it basically restarts the game--but with "demoMode" turned on.
- if (demoWait++ == WAITFORDEMO) {
- state = NORMALSTATE;
- [self restartGameForDemo:YES];
- [controller unpause];
- [[self window] setTitle:"PacMan Demo"];
- [scoreKeeper resetScore];
- } }
-
- // in this state, the "Get Ready!" sign has been put up; we stall for a]
- // while and then we take it away and start up the next level.
- if ((state == READY) || (state == DIEREADY)) {
- for (i=0; i<=3; i++) {
- [ghost[i] move:self];
- }
- if (!(--begin)) {
- eraseReady = YES;
- if (demoMode) [player resetPlayer];
- state = NORMALSTATE;
- updateFlag = YES;
- } }
-
- // make the maze blink on and off at the end of the level.
- if (state == BLINK_LEVEL) {
- if (begin--) {
- if (begin < 32) {
- if (!(cycles & 0x03)) { // make the maze blink
- [maze visible:(![maze isVisible])];
- updateFlag = YES;
- } }
- } else {
- state = READY;
- [maze visible:YES];
- [player resetPlayer];
- [controller nextLevel];
- updateFlag = YES;
- begin = 40;
- }
- }
-
- // stall. when player dies, the player object needs time to get through
- // it's whole sequence.
- if (state == DYING_PAC) {
- if (!(--begin)) { // stall
- if (![player newPlayer]) { // no pacs left == game over.
- [controller gameOver];
- state = GAMEOVER;
- updateFlag = YES;
- } else { // do ready, next pac...
- state = DIEREADY;
- [self startScreen];
- [player resetPlayer];
- updateFlag = YES;
- begin = 40;
- } } }
-
- // this state is the meat of the game. move the player and monsters
- // and deal with player/ghost collisions and eating dots and fruit.
- if (state == NORMALSTATE) {
- // move all the ghosts and the pac
- if (!paused) {
- if (demoMode) lcnt = 2;
- else {
- lcnt = [preferences speed] / 2 + 1; // allow hyper speeds:
- // since 0 <= speed <= 3, we'll move one or two frames per update
- }
- while (lcnt) {
- // figure out how to move player
- [player move:self];
-
- // put up/take away the fruit
- [maze playerPosition:&x :&y]; // get position of fruit
- if ((numFruits < 3)||demoMode) { // only two fruits per level
- if (fruitCount++ == timeToFruit) { // time for new fruit ?
- numFruits++;
- if (numFruits < 3) fruitOn = DRAW;
- NXSetRect(&tRect, x * scale + mazePos.x,
- y * scale + mazePos.y, FRUIT_SIZE * scale,
- FRUIT_SIZE * scale);
- [self rebuildStaticAt:&tRect];
- } else // one or the other
- if (fruitCount == ERASEFRUIT) {
- // it's been there a while... so remove it
- timeToFruit = 200 + (random() & 0x01f0);
- fruitOn = ERASE;
- if (numFruits == 2) numFruits++;
- NXSetRect(&tRect, x * scale + mazePos.x,
- y * scale + mazePos.y, FRUIT_SIZE * scale,
- FRUIT_SIZE * scale);
- [self rebuildStaticAt:&tRect];
- fruitCount = 0;
- }
- }
-
- // handle eating dots
- if ([maze eatDotAt:[player xpos] :[player ypos]]) {
- [customSound playNum:DOTEATSOUND];
- dotEat = YES;
- }
- if ([player pacAlive]) { // if double timing, need to check.
- if ([maze powerDotAt:[player xpos] :[player ypos]]) {
- [customSound playNum:POWERDOTEATSOUND];
- [scoreKeeper resetBonus:ghostId];
- flag = YES;
- dotEat = YES;
- } }
- if (dotEat) {
- [maze lastDot:&x :&y];
- NX_X(&eraseRect) = x * scale + mazePos.x;
- NX_Y(&eraseRect) = y * scale + mazePos.y;
- [self rebuildStaticAt:&eraseRect];
- dotEat = NO;
- }
- if (![maze dots]) {
- state = BLINK_LEVEL;
- begin = 64;
- fruitOn = ERASE; // erase fruit if finished level
- fruitPointCount = 0;
- for (i=0; i<4; i++) ghostPointCount[i] = 0;
- [maze playerPosition:&x :&y]; // get position of fruit
- NXSetRect(&tRect, x * scale + mazePos.x,
- y * scale + mazePos.y, FRUIT_SIZE * scale,
- FRUIT_SIZE * scale);
- [self rebuildStaticAt:&tRect];
- fruitCount = 0;
- }
-
- // see if player ate the fruit
- [maze playerPosition:&x :&y]; // get position of fruit
- if (fruitOn == YES) { // fruit's there...
- if ((abs([player ypos] - y) < GHOST_SIZE / 2) &&
- (abs([player xpos] - x) < GHOST_SIZE / 2)) { // ate it!
- fruitOn = ERASE;
- [customSound playNum:FRUITEATSOUND];
- NXSetRect(&tRect, x * scale + mazePos.x,
- y * scale + mazePos.y, FRUIT_SIZE * scale,
- FRUIT_SIZE * scale);
- [self rebuildStaticAt:&tRect];
- // add value of fruit to the score
- fruitPoints = [scoreKeeper
- addBonusToScore:fruitId advance:NO];
- // tell the player how many points he/she got
- ftx = (x + TEXTOFFSET) * scale + mazePos.x;
- fty = y * scale + mazePos.y;
- ftx2 = ftx + OFFSET_X; fty2 = fty + OFFSET_Y;
-
- // now, align coords to the maze so erase
- // does it's job right (otherwise, we end up SOVERing
- // maze parts twice, and it looks _ugly_!
- ftx -= mazePos.x; fty -= mazePos.y;
- // chop off lower four bits (floor to mult. of 16)
- ftx /= 16 * scale;
- ftx = (ftx << (scale + 3)) + mazePos.x;
- fty /= 16 * scale;
- fty = (fty << (scale + 3)) + mazePos.y;
- ZAPRECT(textEraseRect, ftx, fty);
- fruitPointCount = 48;
- } }
-
- // check for player/ghost collision: (and deal with it)
- if ([maze dots]) {
- for (i=0; i<=3; i++) {
- // tell ghost of power dot
- if (flag) [ghost[i] powerDot:YES];
- [ghost[i] at:&gx :&gy];
- if ((abs(gx - [player xpos]) < GHOST_SIZE / 2) &&
- (abs(gy - [player ypos]) < GHOST_SIZE / 2)) {
- // collision! decide who dies...
- switch ([ghost[i] munch]) {
- case YES : { // player got ghost
- [customSound playNum:MONSTEREATSOUND];
- // (-munch already added any bonus.)
- gtx[i] = (gx + TEXTOFFSET) * scale
- + mazePos.x;
- gty[i] = gy * scale + mazePos.y;
- gtx2[i] = gtx[i] + OFFSET_X;
- gty2[i] = gty[i] + OFFSET_Y;
- ghostPoints[i] = [scoreKeeper
- addBonusToScore:ghostId
- advance:YES];
- // now, align to maze as above
- gtx[i] -= mazePos.x; gtx[i] /= scale * 16;
- gty[i] -= mazePos.y; gty[i] /= scale * 16;
- gtx[i] = (gtx[i] << (3 + scale))
- + mazePos.x;
- gty[i] = (gty[i] << (3 + scale))
- + mazePos.y;
- ZAPRECT(textEraseRect, gtx[i], gty[i]);
- ghostPointCount[i] = 48;
- break;
- }
- case NO : { // ghost got player, so die...
- if (![player pacAlive]) break;
- [customSound playNum:DEADSOUND];
- if (cheatMode) break;
- state = DYING_PAC;
- begin = 48; // stall
- [player pacDie];
- // now, erase the fruit
- fruitOn = ERASE;
- [maze playerPosition:&x :&y];
- NXSetRect(&tRect, x * scale + mazePos.x,
- y * scale + mazePos.y,
- FRUIT_SIZE * scale,
- FRUIT_SIZE * scale);
- [self rebuildStaticAt:&tRect];
- updateFlag = YES;
- break;
- }
- case HARMLESS :
- default : { // eyes do nothing.
- break;
- } } } } }
-
- // figure out where ghosts will go next
- for (i=0; i<=3; i++) {
- [ghost[i] move:self];
- }
- if (lcnt > 1) { // make movement take effect w/o render
- // doing this extra movement gives faster perceived speeds
- for (i=0; i<=3; i++) {
- [ghost[i] moveOneFrame];
- [ghost[i] powerCount];
- }
- [player moveOneFrame];
- }
- lcnt--;
- } } }
- // draw all the changes, if applicable.
- if (updateFlag) [self update];
- /*if ((state != READY) && (state != DIEREADY))*/ [self updateSelf:&bounds :1];
- return self;
- }
-
- // This renders the whole screen, much like updateSelf:: below, but since
- // it always redraws the _entire_ screen, it is unnaceptably inefficient for
- // handling individual animation frames.
- - drawSelf:(NXRect *)rects :(int)rectCount // redraws the screen.
- { // right now, it's stupid and always redraws the whole view.
- //register int i;//, f;
- //int x, y;
- NXPoint pos;
- //NXRect from;//, bezel, mazeRect;
-
- if ([self window] == nil)
- return self; // exit if no window to draw in
-
- if ((state == BLINK_LEVEL) && ![maze isVisible])
- [dirtPile fullRedraw:self :backGround2];
- else [dirtPile fullRedraw:self :staticBuffer];
-
- if (state == GAMEOVER) {
- pos.x = (NX_WIDTH(&bounds) - gameOverSize[scale].width) / 2;
- pos.y = (NX_HEIGHT(&bounds) - gameOverSize[scale].height) / 2;
- [gameOver[scale] composite:NX_SOVER toPoint:&pos];
- }
- if (NXDrawingStatus == NX_PRINTING) { // make sure actors are in print
- [self updateSelf:rects :rectCount];
- }
- NXPing();
- return self;
- }
-
- // This is the main drawing here. It works like drawSelf, but only
- // _changes_ stuff; it doesn't re-draw the whole view each time. This
- // has been done to speed things up. We erase the old, and then redraw
- // all the spots we erased after calculating where things have moved to.
- - updateSelf:(NXRect *)rects :(int)rectCount // redraws the screen.
- { // it redraws only what has changed since last redraw.
- register int i;
- int x, y, j, order[4], flag[4];
- // NXRect from;
-
- if ([self window] == nil) return self; // exit if no window to draw in
- // if blinking maze and maze isn't on screen then this update is unneeded
- // note that technically, even if maze is visible, we could skip out, but
- // we don't because we want the last dot and the monsters, etc. to go
- // away right as we enter BLINK_LEVEL; this won't happen otherwise.
- if (((state == BLINK_LEVEL) && ![maze isVisible]) || (state == GAMEOVER))
- return self;
- [buffer lockFocus];
- // erase ghosts and ghost text fields.
- for (i=0; i<=3; i++) {
- if ((ghostPointCount[i] == WIPETEXT) && (state != GAME_OVER)) {
- ghostPointCount[i] = 0;
- ZAPRECT(textEraseRect, gtx[i], gty[i]);
- CLRRECT(textEraseRect);
- }
- if (state != GAME_OVER) {
- [ghost[i] lastAt:&NX_X(&eraseRect) :&NX_Y(&eraseRect)];
- NX_X(&eraseRect) = NX_X(&eraseRect) * scale + mazePos.x;
- NX_Y(&eraseRect) = NX_Y(&eraseRect) * scale + mazePos.y;
- CLRRECT(eraseRect);
- }
- }
-
- // erase ready text
- if (eraseReady) {
- [maze playerPosition:&x :&y];
- eraseReady = NO;
- ZAPRECT(readyRect, READY_X, READY_Y); // erase ready text
- CLRRECT(readyRect);
- }
-
- // erase fruit text
- if ((fruitPointCount == WIPETEXT) && (state != GAME_OVER)) {
- fruitPointCount = 0;
- ZAPRECT(textEraseRect, ftx, fty);
- CLRRECT(textEraseRect);
- }
-
- // erase the player's PacMan
- if (state != GAME_OVER) {
- [player lastAt:&NX_X(&eraseRect) :&NX_Y(&eraseRect)];
- NX_X(&eraseRect) = NX_X(&eraseRect) * scale + mazePos.x;
- NX_Y(&eraseRect) = NX_Y(&eraseRect) * scale + mazePos.y;
- CLRRECT(eraseRect);
- }
-
- // put the player's Pac on the screen if in a state where it's visible.
- if ((state != BLINK_LEVEL) || ((state == BLINK_LEVEL) && (begin > 31))) {
- [player renderAt:mazePos.x :mazePos.y
- move:((!paused) && (state == NORMALSTATE))];
- [player lastAt:&NX_X(&eraseRect) :&NX_Y(&eraseRect)];
- NX_X(&eraseRect) = NX_X(&eraseRect) * scale + mazePos.x;
- NX_Y(&eraseRect) = NX_Y(&eraseRect) * scale + mazePos.y;
- [dirtPile addRegion:&eraseRect];
- }
-
- // put ghosts on screen if we're in a state where they are visible.
- if ((state != DYING_PAC) && (state != BLINK_LEVEL) &&
- (state != GAMEOVER)) {
- for (i=0; i<=3; i++) {
- [ghost[i] renderAt:mazePos.x :mazePos.y move:
- ((!paused) && (state == NORMALSTATE))];
- [ghost[i] lastAt:&NX_X(&eraseRect) :&NX_Y(&eraseRect)];
- NX_X(&eraseRect) = NX_X(&eraseRect) * scale + mazePos.x;
- NX_Y(&eraseRect) = NX_Y(&eraseRect) * scale + mazePos.y;
- [dirtPile addRegion:&eraseRect];
- } }
-
- // draw text messages
- if (fruitPointCount) {
- drawScore(ftx2, fty2, POINTS, fruitPoints);
- ZAPRECT(textEraseRect, ftx, fty);
- }
- for (i=0; i<4; i++) flag[i] = NO;
- for (i=0; i<4; i++) { // sort scores to draw in lowest to highest order
- // A selection sort is used. O(n^2) but n=4 so who cares?
- int next = 0; int val = 100000;
- for (j=0; j<4; j++) { // find next lowest, unused score value
- if (!flag[j] && (ghostPoints[j] < val)) {
- val = ghostPoints[j]; next = j;
- }
- }
- flag[next] = YES; order[i] = next;
- }
- for (i=0; i<4; i++) if (ghostPointCount[order[i]]) {
- drawScore(gtx2[order[i]], gty2[order[i]], POINTS,
- ghostPoints[order[i]]);
- ZAPRECT(textEraseRect, gtx[order[i]], gty[order[i]]);
- }
- if ((state == READY) || (state == DIEREADY)) {
- [maze playerPosition:&x :&y];
- drawReady(READY_X + 10 * scale, READY_Y + OFFSET_Y, POINTS);
- ZAPRECT(readyRect, READY_X, READY_Y);
- }
- [buffer unlockFocus];
-
- // housekeeping for the graphics:
- [self lockFocus];
- [dirtPile doRedraw:buffer]; // flush buffer out
- [self unlockFocus];
- if (fruitOn == ERASE) fruitOn = NO;
- NXPing();
- erasePwr = NO; // we've listened to this flag, so turn it off now.
- return self;
- }
-
- // Handle player movement. We respond to the arrow keys. If we don't
- // recognize the key, we pass it on. This method just shunts the key
- // off to the player object, which is what really deals with it.
- - keyDown:(NXEvent *)myevent
- {
- unsigned short charCode;
- unsigned short charSet;
- register int i;
-
- if (!myevent) return self; // if no event when coalescing, go away
- PSobscurecursor();
- #ifdef CHEATMODES
- // secret cheat mode: control-c
- if (myevent->data.key.charCode == 0x03) {
- cheatMode = YES;
- fflush(stderr);
- [preferences setUnfair]; // don't allow net high scores. local OK
- [customSound playNum:POWERDOTEATSOUND];
- return self;
- }
- // secret cheat: control-d (eats a power dot)
- if (myevent->data.key.charCode == 0x04) {
- for (i=0; i<4; i++) [ghost[i] powerDot:YES];
- [preferences setUnfair]; // don't allow net high scores. local OK
- [customSound playNum:POWERDOTEATSOUND];
- [scoreKeeper resetBonus:ghostId];
- return self;
- }
- // secret cheat: control-f (put fruit on screen)
- if (myevent->data.key.charCode == 0x06) {
- fruitCount = timeToFruit - 1;
- numFruits = 0;
- [preferences setUnfair]; // don't allow net high scores. local OK
- return self;
- }
- // secret cheat: control-l (advance a level instantly)
- if (myevent->data.key.charCode == 0x0c) {
- NXRect tRect;
- int x, y;
- state = BLINK_LEVEL;
- begin = 64;
- fruitPointCount = 0; for (i=0; i<4; i++) ghostPointCount[i] = 0;
- fruitOn = ERASE; // erase fruit if finished level
- [maze playerPosition:&x :&y]; // get position of fruit
- NXSetRect(&tRect, x * scale + mazePos.x,
- y * scale + mazePos.y, FRUIT_SIZE * scale,
- FRUIT_SIZE * scale);
- [self rebuildStaticAt:&tRect];
- fruitCount = 0;
- [preferences setUnfair]; // don't allow net high scores. local OK
- return self;
- }
- #endif
- // check for arrow keys or space bar
- if (!(myevent->flags&(NX_CONTROLMASK|NX_ALTERNATEMASK|NX_COMMANDMASK))) {
- charCode = myevent->data.key.charCode;
- charSet = myevent->data.key.charSet;
-
- if (charSet == NX_SYMBOLSET) { // symbol set contains the arrows
- if (charCode == 0xAD) { // Up Arrow
- [player newDirection:PAC_UP];
- if (paused) [controller unpause];
- return self;
- } else if (charCode == 0xAF) { // Down Arrow
- [player newDirection:PAC_DOWN];
- if (paused) [controller unpause];
- return self;
- } else if (charCode == 0xAC) { // Left Arrow
- [player newDirection:PAC_LEFT];
- if (paused) [controller unpause];
- return self;
- } else if (charCode == 0xAE) { // Right Arrow
- [player newDirection:PAC_RIGHT];
- if (paused) [controller unpause];
- return self;
- }
- } else if (myevent->data.key.charCode == ' ') { // Space Bar
- [player newDirection:PAC_STOP];
- if (paused) [controller unpause];
- return self;
- } else if ((myevent->data.key.charCode == 'a') && cheatMode) {
- #ifdef CHEATMODES
- // abort pac -- it's the only way you can die in cheat mode.
- [customSound playNum:DEADSOUND];
- state = DYING_PAC;
- begin = 48; // stall
- [player pacDie];
- #endif
- } else {
- [super keyDown:myevent];
- }
- } else [super keyDown:myevent];
- [self keyDown:[NXApp peekAndGetNextEvent:NX_KEYDOWN]]; // coalesce keydowns
- // this is done recursively...primitive, but easy to implement :-)
- return self;
- }
-
- // set up the screen at the start of a new level. Loads in the maze.
- - setUpScreen
- {
- [maze makeMaze:screens[(([controller level] >= NUMSCREENS) ?
- (NUMSCREENS - 1) : [controller level]) - 1]];
- fruitOn = NO;
- [self rebuildStaticBuffer];
- [self startScreen];
- timeToFruit = 200 + (random() & 0x01f0);
- fruitCount = 0;
- numFruits = 0;
- [[scoreKeeper bonusTracker:fruitId] advanceBonus]; // changes with level
-
- [super setUpScreen];
- return self;
- }
-
- // This lets us put the ghosts back where they are at the start of the level.
- // This is separate from above because the above also resets the dots, and if
- // the player dies, we only want to reset the ghosts, not the dots, too.
- - startScreen
- {
- const int *gh; int i;
-
- gh = [maze ghosts]; // pointer to array of ghost coordinates
- for (i=0; i<=3; i++) { // re-initialize each ghost
- [ghost[i] initGhost:i player:player maze:maze at:gh[i*2] :gh[i*2+1]];
- [ghost[i] setScale:scale];
- ghostPointCount[i] = 0;
- }
- fruitPointCount = 0;
- // don't let fruit come out too quickly
- if (timeToFruit - fruitCount < 200)
- timeToFruit += 250;
-
- return self;
- }
-
- - restartGame
- {
- return [self restartGameForDemo:NO];
- }
-
- - restartGameForDemo:(BOOL)doingDemo
- {
- // go to READY state to start game; but want DIEREADY so we don't advance
- // the level; the controller, by virtue of calling this method, has already
- // advanced the level.
- state = DIEREADY;
- begin = 100;
- fruitOn = NO;
-
- // make sure that all artifacts of demo mode are gone
- demoWait = 0;
- cheatMode = NO;
- if (demoMode) {
- demoMode = NO;
- [[self window] setTitle:"PacMan"];
- }
-
- [scoreKeeper resetScore]; // clear the score
- [scoreKeeper resetBonus:(-1)]; // clear all bonuses
- [self getPreferences]; // make sure we're up to date
- [player newPlayer]; // get a new pac to play with
- // (above always sets up the pac, but in demoMode, the gameBrain hasn't
- // given us 3 pacs, so we end up taking a negative # of pacs, which means
- // demo mode will end as soon as the pac dies.)
- if (doingDemo) demoMode = YES;
- // cut off the info panel sound (if playing) (only if not demo mode)
- else [[controller infoController] stopSound:self];
- [customSound shutUpUntil:[[DAYTime alloc] initWithCurrentTime]];
- [self update]; [self updateSelf:&bounds :1]; NXPing(); // show screen
- // start up the sound player object with the "start game" music
- if ([preferences music]) [customSound playNum:STARTLEVELSOUND];
- return self;
- }
-
- - setBackgroundFile:(const char *)fileName andRemember:(BOOL)remember
- {
- NXRect bezel = {{NX_X(&bounds) + 5, NX_Y(&bounds) + 5},
- {NX_WIDTH(&bounds) - 10, NX_HEIGHT(&bounds) - 10}};
-
- [super setBackgroundFile:fileName andRemember:remember];
- [backGround2 lockFocus];
- NXDrawGrayBezel(&bezel, &bounds);
- NXFrameRectWithWidth(&bounds, 5);
- NX_WIDTH(&bezel) -= 6; NX_HEIGHT(&bezel) -= 6;
- NX_X(&bezel) += 3; NX_Y(&bezel) += 3;
- [self drawBackground:&bezel];
- [backGround2 unlockFocus];
- [self rebuildStaticBuffer];
- [self update];
- return self;
- }
-
- - buildColorBackground
- {
- NXRect bezel = {{NX_X(&bounds) + 5, NX_Y(&bounds) + 5},
- {NX_WIDTH(&bounds) - 10, NX_HEIGHT(&bounds) - 10}};
-
- [backGround2 lockFocus];
- NXDrawGrayBezel(&bezel, &bounds);
- NXFrameRectWithWidth(&bounds, 5);
- NX_WIDTH(&bezel) -= 6; NX_HEIGHT(&bezel) -= 6;
- NX_X(&bezel) += 3; NX_Y(&bezel) += 3;
- [self drawBackground:&bezel];
- [backGround2 unlockFocus];
- [self rebuildStaticBuffer];
- return self;
- }
-
- - acceptColor:(NXColor)color atPoint:(const NXPoint *)aPoint
- { // override to redraw background buffer.
- backIsColor = YES;
- backColor = color;
- [self buildColorBackground];
- [[self writeColor] update];
- return self;
- }
-
- - rebuildStaticBuffer
- {
- NXRect mazeRect = {{0, 0},
- {GHOST_SIZE * BLOCK_WIDTH, GHOST_SIZE * BLOCK_HEIGHT}};
- int x, y;
- register int f;
- NXRect from;
- NXPoint pos;
-
- [staticBuffer lockFocus];
- [backGround2 composite:NX_COPY fromRect:&bounds toPoint:&(bounds.origin)];
- [maze render:&mazeRect at:&mazePos];
- [maze playerPosition:&x :&y];
- if (fruitOn) {
- if (fruitOn != ERASE) {
- // decide which fruit to draw
- f = fruits[(([controller level] > FRUIT_LEVELS) ?
- FRUIT_LEVELS : [controller level])];
- // draw the fruit
- pos.x = x * scale + mazePos.x;
- pos.y = y * scale + mazePos.y;
- NXSetRect(&from,
- (f % FRUIT_PER_ROW) * FRUIT_SIZE * scale,
- (f / FRUIT_PER_ROW) * FRUIT_SIZE * scale,
- FRUIT_SIZE * scale, FRUIT_SIZE * scale);
- [fruit[scale] composite:NX_SOVER fromRect:&from toPoint:&pos];
- fruitOn = YES;
- } else fruitOn = NO;
- }
- [staticBuffer unlockFocus];
-
- [buffer lockFocus];
- [staticBuffer composite:NX_COPY fromRect:&bounds toPoint:&(bounds.origin)];
- [buffer unlockFocus];
- [dirtPile addRegion:&bounds];
-
- return self;
- }
-
- - rebuildStaticAt:(NXRect *)rect;
- { // assumes that bezel is intact, so doesn't draw it
- NXRect mazeRect = {{(NX_X(rect) - mazePos.x) / scale,
- (NX_Y(rect) - mazePos.y) / scale},
- {(NX_WIDTH(rect) - 1.0) / scale,
- (NX_HEIGHT(rect) - 1.0) / scale}};
- NXRect xRect = {{0.0, 0.0}, {GHOST_SIZE * scale, GHOST_SIZE * scale}};
- NXPoint pos;
- NXRect from;
- register int f;
- int x, y;
-
- [staticBuffer lockFocus];
- [super rebuildStaticAt:rect];
- [maze render:&mazeRect at:&mazePos];
- // see if rect intersects fruit && fruit is on screen
- [maze playerPosition:&x :&y];
- NX_X(&xRect) = x * scale + mazePos.x;
- NX_Y(&xRect) = y * scale + mazePos.y;
- if (NXIntersectsRect(&xRect, rect) && fruitOn) {
- if (fruitOn != ERASE) {
- // decide which fruit to draw
- f = fruits[(([controller level] > FRUIT_LEVELS) ?
- FRUIT_LEVELS : [controller level])];
- // draw the fruit
- pos.x = x * scale + mazePos.x;
- pos.y = y * scale + mazePos.y;
- NXSetRect(&from,
- (f % FRUIT_PER_ROW) * FRUIT_SIZE * scale,
- (f / FRUIT_PER_ROW) * FRUIT_SIZE * scale,
- FRUIT_SIZE * scale, FRUIT_SIZE * scale);
- [fruit[scale] composite:NX_SOVER fromRect:&from
- toPoint:&(xRect.origin)];
- fruitOn = YES;
- } else fruitOn = NO;
- }
- [staticBuffer unlockFocus];
-
- [buffer lockFocus];
- [staticBuffer composite:NX_COPY fromRect:rect toPoint:&(rect->origin)];
- [buffer unlockFocus];
- [dirtPile addRegion:rect];
-
- return self;
- }
-
- - blinkPowerDot // update static buffer where power dots are.
- {
- const int *pd; // pointer to array of power dot coords
- register int i;
-
- [maze blinkPowerDot];
- pd = [maze powerDot]; // get power dot coords
- for (i=0; i<=3; i++) { // erase power dot every time it blinks.
- NX_X(&eraseRect) = pd[i * 2 ] * GHOST_SIZE * scale + mazePos.x;
- NX_Y(&eraseRect) = pd[i * 2 + 1] * GHOST_SIZE * scale + mazePos.y;
- [self rebuildStaticAt:&eraseRect];
- }
- return self;
- }
-
- - (int)scale
- {
- return scale;
- }
-
- - setScale:(int)newScale
- {
- int i;
-
- if ((newScale < 1) || (newScale > 2)) return self;
- if (newScale == scale) return self; // already that size.
-
- scale = newScale;
-
- for (i=0; i<4; i++) {
- [ghost[i] setScale:scale];
- }
- [player setScale:scale];
- [maze setScale:scale];
-
- // resize self and windows
- [window disableFlushWindow];
- [window disableDisplay];
- [self sizeTo:windowX[scale] :windowY[scale]];
- NXSetRect(&eraseRect, 0, 0, GHOST_SIZE * scale, GHOST_SIZE * scale);
- NXSetRect(&readyRect, 0, 0, GHOST_SIZE * scale * 5, GHOST_SIZE * scale);
- NXSetRect(&textEraseRect, 0, 0,
- 3 * GHOST_SIZE * scale, 2 * GHOST_SIZE * scale);
-
-
- // rebuild all the offscreen buffers.
- [buffer free];
- [staticBuffer free];
- [backGround2 free];
- buffer = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size];
- staticBuffer = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size];
- backGround2 = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size];
-
- if (backIsColor) [self buildColorBackground];
- else [self setBackgroundFile:NXGetDefaultValue([NXApp appName],
- "BackGround") andRemember:NO];
-
- [self rebuildStaticBuffer];
- [[window delegate] windowDidMove:window];
- [window sizeWindow:windowX[scale] :windowY[scale]];
- [window reenableFlushWindow];
- [window reenableDisplay];
- [[window display] flushWindowIfNeeded];
- [self update];
- return self;
- }
-
- @end
-